Day16 的文章中,我們透過 query string 與 props 來傳遞「keyword」,進而實現搜尋功能。同時,為了確保安全性,我們對使用者的輸入進行了嚴格的過濾:只允許英文、數字、底線、連字符號和空白,且長度限制在 100 個字符內。
這樣的限制看似簡單,但背後的意義卻十分重大。假如這些限制不存在,系統可能會面臨哪些安全威脅呢?
前端資安領域中,開發者或許會經常聽到「XSS攻擊」。但究竟什麼是 XSS?又有哪些策略能夠有效地預防這種威脅?
解開這些疑惑後,我們將深入探索 Vue 框架如何提供協助,預先為開發者提供了一系列的安全防護措施。
在這個示範中,我們會展示如何透過讀取 Query String 並直接插入到 HTML 中,來執行一個簡單的 XSS 攻擊。
以下是一個簡單的範例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>XSS Demo</title>
</head>
<body>
<div id="content"></div>
<script>
// 取得 query string
const queryString = window.location.search.substring(1);
// 將 query string decode 直接放入 HTML
document.getElementById('content').innerHTML = decodeURIComponent(queryString);
</script>
</body>
</html>
注意
:該代碼僅供示範之用。在實際應用中,這種方式可能導致安全漏洞,所以強烈建議避免使用。
當我們輸入 /?HelloWorld
訪問頁面時,會看到 HelloWorld
:
看起來毫無威脅?
但如果更改為使用 /?<img src="x" onerror="alert('XSS')">
訪問,則會觸發並執行程式碼中未定義的 script!
由於不正當的資料處理和不完整的防護機制常成為攻擊者的利用點,多數瀏覽器和框架為防範 XSS 攻擊已經提供了一系列的安全措施。
(也因此,其實範例的程式碼還是刻意寫出帶有漏洞的程式碼,只為了說明概念。)
那究竟是哪些程式碼片段造成 XSS 攻擊生效的呢?
decodeURIComponent
許多現代瀏覽器會自動轉義某些字符以提高安全性。特別在 URL 或 query string 中,字符如 <
和 >
會被轉換為它們的 URL 編碼形式,例如 %3C
和 %3E
。
如果沒有使用 decodeURIComponent
,而是直接把 queryString
assign 給 innerHTML
,這個攻擊甚至不會被觸發。
innerHTML
現代的教學文獻都會建議初學者使用 .textContent
或其他更安全的方法來處理用戶輸入,也會標註 innerHTML
的不安全性。
整體來說,該程式碼在讀取 query string 之後,先使用解碼並插入 HTML。
當輸入 <img src="x" onerror="alert('XSS')">
時,它會導致瀏覽器解析這段代碼,並當圖像加載失敗時,觸發 onerror 事件, XSS 攻擊生效,彈出警告框 "XSS"。
跨站腳本攻擊(Cross-Site Scripting,縮寫:XSS)是一種在網頁應用程式中的安全漏洞,它允許攻擊者將惡意腳本注入到其他使用者的網頁中。當其他使用者瀏覽這些帶有惡意腳本的網頁時,這些腳本將在他們的瀏覽器中運行。這樣,攻擊者可以繞過同源策略,從而訪問其他使用者的資料或在受害者的瀏覽器中執行任意代碼。
那麼如果 XSS 發生了,會導致什麼不良影響呢?
在資訊安全的世界裡,挑戰是永無止境的,因為每次技術進步,新的威脅和漏洞也隨之出現。
正如程式是由人類編寫,完美無瑕的程式碼幾乎是一個遙不可及的理想。不過,這並不代表我們不能努力追求更高的安全標準和更完善的保護措施。
以下是一些基礎防範 XSS 的策略:
eval()
:不要使用 eval()
或相似的 JavaScript 函數,因為這會執行動態代碼,很容易被濫用。Vue.js,作為當前最受歡迎的前端框架之一,自然也對安全問題給予了高度的關注。
開發者如果正確使用 Vue,可以有效地避免多種常見的前端安全問題,特別是 XSS 攻擊。
{{ message }}
)時,Vue 會自動將 message
中的任何特殊字符轉義。v-html
指令,但使用時需要特別小心)。文本轉義主要是為了防止 XSS 攻擊。在 XSS 攻擊中,攻擊者會試圖注入惡意腳本到網頁中。如果網站不小心將這些腳本呈現給使用者,那麼當使用者訪問該頁面時,這些惡意腳本就會被執行。
一個留言的例子
假設網站有架設留言區供使用者交流,而一個攻擊者在評論中輸入了 <script>alert('Hacked!');</script>
。如果這個輸入直接被渲染到頁面上,那麼每當有人查看該評論時,都會看到一個彈窗。實際上,攻擊者可以執行更加惡意的操作,如竊取 cookie 或其他敏感信息。
因此,我們需要文本轉義來確保所有的輸入都被視為純文本,而不是執行代碼。
當我們在 Vue 模板中使用 {{ message }}
進行資料綁定時,Vue 會使用一個內部的轉義函數將 message
中的特殊字符(如 <
, >
, &
等)轉換為它們的 HTML 實體形式,例如 <
, >
和 &
。
這意味著即使 message
中包含惡意代碼或腳本,它也只會被視為純文本並被正確地呈現,而不會被執行。
當使用 v-bind
或簡寫為 :
進行屬性值綁定時,Vue 會確保內容作為純文本解析,而非 HTML 或腳本。
例如,v-for
和 v-if
這些指令都經過設計,確保它們不會執行任意的 JavaScript 代碼。
v-html
如果開發者真的需要渲染原始的 HTML,例如渲染 Rich Editor 產生的內容,Vue 提供了 v-html
指令。
但使用上必須非常謹慎,因為直接使用 v-html
意味著插入真實的 HTML,且不進行轉義,如此一來就有可能導致 XSS 攻擊,特別是當渲染的內容來自使用者輸入時,開發者需要自行對渲染內容進行轉義或過濾。
以 Day16 文章中的搜尋功能舉例
只需在 src/views/BlogSearchView.vue
的 template 區塊中添加以下這行程式碼:
<div v-html="keyword" />
當使用者在搜尋框中輸入 <img src="x" onerror="alert('XSS')">
,則XSS攻擊將被成功觸發。
這是因為我們直接利用 v-html
渲染了未經過任何過濾的 props.keyword
,進而造成了這樣的安全風險。
今天,我們一同探索了 XSS 攻擊的基礎知識,相信讀者對於資安風險的意識會更有警覺性。
還是老話一句,理解這些潛在的風險及其背後的原理十分重要。只有當我們明白哪些做法是不好的,我們才能撰寫更安全的程式。
我們還談到 Vue 提供了哪些安全機制,讓開發者可以專注在應用的核心功能,省下不少擔憂資安的心力。Vue 的文本轉義機制,是我們對抗邪惡 XSS 攻擊的第一道防線。最後,透過實際的例子和操作,希望讀者能更加熟悉這個機制。
我們在 Day16 觀察到的問題還有一個:Header 和 Sidebar 中都存在著重複的搜尋功能邏輯。
為了解決這個問題,明天我們將會深入介紹 Vue Composition API 中的 Composable,這將幫助我們更有效率地重構和管理那些重複的程式碼。敬請期待!
Prototype pollution 是一種攻擊手法,攻擊者藉由修改原型鏈來影響所有的物件。如果不當地處理使用者輸入,這個攻擊手法可能會在 JavaScript 應用中發生。
以下是一個基本的範例,展示如何透過 query string 實施 prototype pollution:
const url = require('url');
const querystring = require('querystring');
function merge(target, source) {
for (let key in source) {
if (typeof source[key] === 'object') {
if (!target[key]) target[key] = {};
merge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
}
function processQueryString(queryStr) {
const params = querystring.parse(queryStr);
let obj = {};
merge(obj, params);
return obj;
}
const userInput = 'prototype.polluted=true';
const processedData = processQueryString(userInput);
console.log(({}).polluted); // outputs "true"
這個範例中,我們模擬了從 URL 的 query string 取得的輸入,並透過 merge
函數將其合併到目標物件上。由於我們沒有做任何檢查或過濾,攻擊者可以傳入如 'prototype.polluted=true'
的值,從而修改原型鏈,這導致所有的物件都帶有了 polluted
屬性。
為避免這種攻擊,你應該避免使用遞迴合併或直接將使用者輸入賦值給物件,特別是當使用者輸入的鍵或值可以動態更改時。